/* (C) 2012 Pragmatic Software This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/ */ package com.googlecode.networklog; import android.util.Log; import android.app.Activity; import android.os.Bundle; import android.widget.TextView; import android.widget.LinearLayout; import android.widget.ExpandableListView; import android.widget.ExpandableListView.OnGroupExpandListener; import android.widget.ExpandableListView.OnGroupCollapseListener; import android.widget.ExpandableListView.OnChildClickListener; import android.widget.AdapterView.OnItemLongClickListener; import android.widget.BaseExpandableListAdapter; import android.widget.ImageView; import android.widget.Filter; import android.widget.Filterable; import android.view.View; import android.view.ViewGroup; import android.content.Context; import android.content.Intent; import android.view.LayoutInflater; import android.graphics.drawable.Drawable; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.os.SystemClock; import android.text.Html; import android.text.Spanned; import android.widget.TextView.BufferType; import android.util.TypedValue; import android.os.Parcelable; import android.view.MenuItem; import android.view.MenuInflater; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.widget.ExpandableListView.ExpandableListContextMenuInfo; import android.net.Uri; /* newer API 11 clipboard unsupported on older APIs import android.content.ClipboardManager; import android.content.ClipData; */ /* use older clipboard API to support older devices */ import android.text.ClipboardManager; import android.support.v4.app.Fragment; import java.lang.StringBuilder; import java.util.Arrays; import java.util.Set; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; public class AppFragment extends Fragment { // groupData bound to adapter, and filtered public ArrayList<GroupItem> groupData; // groupDataBuffer used to buffer incoming log entries and to hold original list data for filtering public ArrayList<GroupItem> groupDataBuffer; public boolean groupDataBufferIsDirty = false; public boolean needsRefresh = false; private ExpandableListView listView; private CustomAdapter adapter; public Sort preSortBy; public Sort sortBy; private Comparator<GroupItem> preSortMethod = null; private Comparator<GroupItem> sortMethod = null; private Comparator<GroupItem> sortMethodPreSort = null; private ChildrenSort childrenSortMethod = null; public boolean roundValues; public GroupItem cachedSearchItem; private ListViewUpdater updater; // remember last index return by getItemByAppUid to optimize-out call to binarySearch int lastGetItemByAppUidIndex = -1; private NetworkLog parent = null; private boolean gotInstalledApps = false; private boolean doNotRefresh = false; public class GroupItem { protected ApplicationsTracker.AppEntry app; protected long uploadThroughput; protected long downloadThroughput; protected long totalThroughput; protected String throughputString; protected long sentPackets; protected long receivedPackets; protected long totalPackets; protected long sentBytes; protected long receivedBytes; protected long totalBytes; protected long lastTimestamp; // childrenData bound to adapter, holds original list of children protected HashMap<String, ChildItem> childrenData; // holds filtered list of children // used in place of childrenData in getView, if non-empty protected HashMap<String, ChildItem> childrenDataFiltered; // holds sorted keys into childrenData protected String[] childrenDataSorted; protected boolean childrenNeedSort = false; protected boolean childrenAreFiltered = false; protected boolean childrenAreDirty = false; protected boolean isExpanded = false; @Override public String toString() { return "(" + app.uidString + ") " + app.name; } } public class ChildItem { protected String proto; // protocol (udp, tcp, igmp, icmp, etc) protected int sentPackets; protected int sentBytes; protected long sentTimestamp; protected int sentPort; protected String sentAddress; protected String out; // interface (rmnet, wifi, etc) protected int receivedPackets; protected int receivedBytes; protected long receivedTimestamp; protected int receivedPort; protected String receivedAddress; protected String in; // interface (rmnet, wifi, etc) public String toString() { // todo: resolver here return sentAddress + ":" + sentPort + " -> " + receivedAddress + ":" + receivedPort; } } public void clear() { synchronized(groupData) { synchronized(groupDataBuffer) { for(GroupItem item : groupDataBuffer) { synchronized(item.childrenData) { List<String> list = new ArrayList<String>(item.childrenData.keySet()); Iterator<String> itr = list.iterator(); while(itr.hasNext()) { String host = itr.next(); ChildItem childData = item.childrenData.get(host); } item.childrenData.clear(); item.childrenDataFiltered.clear(); item.childrenDataSorted = null; item.childrenAreFiltered = false; } } groupDataBuffer.clear(); groupData.clear(); groupDataBufferIsDirty = false; } } getInstalledApps(false); lastGetItemByAppUidIndex = -1; } protected static class SortAppsByBytes implements Comparator<GroupItem> { public int compare(GroupItem o1, GroupItem o2) { return o1.totalBytes > o2.totalBytes ? -1 : (o1.totalBytes == o2.totalBytes) ? 0 : 1; } } protected static class SortAppsByThroughput implements Comparator<GroupItem> { public int compare(GroupItem o1, GroupItem o2) { return o1.totalThroughput > o2.totalThroughput ? -1 : (o1.totalThroughput == o2.totalThroughput) ? 0 : 1; } } protected static class SortAppsByPackets implements Comparator<GroupItem> { public int compare(GroupItem o1, GroupItem o2) { return o1.totalPackets > o2.totalPackets ? -1 : (o1.totalPackets == o2.totalPackets) ? 0 : 1; } } protected static class SortAppsByTimestamp implements Comparator<GroupItem> { public int compare(GroupItem o1, GroupItem o2) { return o1.lastTimestamp > o2.lastTimestamp ? -1 : (o1.lastTimestamp == o2.lastTimestamp) ? 0 : 1; } } protected static class SortAppsByName implements Comparator<GroupItem> { public int compare(GroupItem o1, GroupItem o2) { return o1.app.name.compareToIgnoreCase(o2.app.name); } } protected static class SortAppsByUid implements Comparator<GroupItem> { public int compare(GroupItem o1, GroupItem o2) { return o1.app.uid < o2.app.uid ? -1 : (o1.app.uid == o2.app.uid) ? 0 : 1; } } protected interface ChildrenSort { public void setGroupItem(GroupItem item); } protected static class SortChildrenByBytes implements Comparator<String>, ChildrenSort { GroupItem item; public SortChildrenByBytes() { } public SortChildrenByBytes(GroupItem item) { this.item = item; } public void setGroupItem(GroupItem item) { this.item = item; } public int compare(String o1, String o2) { ChildItem c1; ChildItem c2; if(item.childrenAreFiltered) { c1 = item.childrenDataFiltered.get(o1); c2 = item.childrenDataFiltered.get(o2); } else { c1 = item.childrenData.get(o1); c2 = item.childrenData.get(o2); } long totalBytes1 = c1.sentBytes + c1.receivedBytes; long totalBytes2 = c2.sentBytes + c2.receivedBytes; return totalBytes1 > totalBytes2 ? -1 : (totalBytes1 == totalBytes2) ? 0 : 1; } } protected static class SortChildrenByPackets implements Comparator<String>, ChildrenSort { GroupItem item; public SortChildrenByPackets() { } public SortChildrenByPackets(GroupItem item) { this.item = item; } public void setGroupItem(GroupItem item) { this.item = item; } public int compare(String o1, String o2) { ChildItem c1; ChildItem c2; if(item.childrenAreFiltered) { c1 = item.childrenDataFiltered.get(o1); c2 = item.childrenDataFiltered.get(o2); } else { c1 = item.childrenData.get(o1); c2 = item.childrenData.get(o2); } long totalPackets1 = c1.sentPackets + c1.receivedPackets; long totalPackets2 = c2.sentPackets + c2.receivedPackets; return totalPackets1 > totalPackets2 ? -1 : (totalPackets1 == totalPackets2) ? 0 : 1; } } protected static class SortChildrenByTimestamp implements Comparator<String>, ChildrenSort { GroupItem item; public SortChildrenByTimestamp() { } public SortChildrenByTimestamp(GroupItem item) { this.item = item; } public void setGroupItem(GroupItem item) { this.item = item; } public int compare(String o1, String o2) { ChildItem c1; ChildItem c2; if(item.childrenAreFiltered) { c1 = item.childrenDataFiltered.get(o1); c2 = item.childrenDataFiltered.get(o2); } else { c1 = item.childrenData.get(o1); c2 = item.childrenData.get(o2); } long timestamp1 = c1.sentTimestamp; long timestamp2 = c2.sentTimestamp; if(c1.receivedTimestamp > timestamp1) { timestamp1 = c1.receivedTimestamp; } if(c2.receivedTimestamp > timestamp2) { timestamp2 = c2.receivedTimestamp; } return timestamp1 > timestamp2 ? -1 : (timestamp1 == timestamp2) ? 0 : 1; } } protected void setPreSortMethod() { switch(preSortBy) { case UID: preSortMethod = new SortAppsByUid(); break; case NAME: preSortMethod = new SortAppsByName(); break; default: Log.e("NetworkLog", "Unknown pre-sort method: " + preSortBy); preSortMethod = new SortAppsByName(); NetworkLog.settings.setPreSortBy(Sort.NAME); } } protected void preSortData() { synchronized(groupData) { Collections.sort(groupData, preSortMethod); } } protected void setSortMethod() { switch(sortBy) { case UID: sortMethodPreSort = null; sortMethod = new SortAppsByUid(); childrenSortMethod = new SortChildrenByBytes(); break; case NAME: sortMethodPreSort = null; sortMethod = new SortAppsByName(); childrenSortMethod = new SortChildrenByBytes(); break; case THROUGHPUT: sortMethodPreSort = new SortAppsByTimestamp(); sortMethod = new SortAppsByThroughput(); childrenSortMethod = new SortChildrenByTimestamp(); break; case PACKETS: sortMethodPreSort = null; sortMethod = new SortAppsByPackets(); childrenSortMethod = new SortChildrenByPackets(); break; case BYTES: sortMethodPreSort = null; sortMethod = new SortAppsByBytes(); childrenSortMethod = new SortChildrenByBytes(); break; case TIMESTAMP: sortMethodPreSort = null; sortMethod = new SortAppsByTimestamp(); childrenSortMethod = new SortChildrenByTimestamp(); break; default: Log.e("NetworkLog", "Unknown sort method: " + preSortBy); sortMethodPreSort = null; sortMethod = new SortAppsByBytes(); childrenSortMethod = new SortChildrenByBytes(); NetworkLog.settings.setSortBy(Sort.BYTES); } } protected void sortData() { synchronized(groupData) { if(sortMethodPreSort != null) { Collections.sort(groupData, sortMethodPreSort); } Collections.sort(groupData, sortMethod); } } public void sortChildren() { synchronized(groupData) { for(GroupItem item : groupData) { item.childrenNeedSort = true; } } } public void setDoNotRefresh(boolean value) { doNotRefresh = value; } public void refreshAdapter() { if(doNotRefresh) { return; } if(listView == null) { return; } int index = listView.getFirstVisiblePosition(); View v = listView.getChildAt(0); int top = (v == null) ? 0 : v.getTop(); adapter.notifyDataSetChanged(); if(MyLog.enabled && MyLog.level >= 5) { MyLog.d(5, "Refreshed AppFragment adapter"); } listView.setSelectionFromTop(index, top); int size = adapter.getGroupCount(); for(int i = 0; i < size; i++) { if(((GroupItem)adapter.getGroup(i)).isExpanded == true) { listView.expandGroup(i); } else { listView.collapseGroup(i); } } } public void addApp(ApplicationsTracker.AppEntry app) { if(groupDataBuffer == null) { return; } synchronized(groupDataBuffer) { GroupItem item = null; for(GroupItem i : groupDataBuffer) { if(i.app.packageName.equals(app.packageName)) { item = i; break; } } if(item == null) { item = new GroupItem(); item.app = app; item.lastTimestamp = 0; item.childrenData = new HashMap<String, ChildItem>(); item.childrenDataFiltered = new HashMap<String, ChildItem>(); if(NetworkLogService.throughputBps) { item.throughputString = "0bps/0bps"; } else { item.throughputString = "0B/0B"; } groupData.add(item); groupDataBuffer.add(item); } else { item.app = app; } // groupDataBuffer must always be sorted by UID for binary search Collections.sort(groupDataBuffer, new SortAppsByUid()); lastGetItemByAppUidIndex = -1; } if(NetworkLog.filterTextInclude.length() > 0 || NetworkLog.filterTextExclude.length() > 0) { setFilter(""); } else { refreshAdapter(); } } public void removeApp(String packageName) { if(groupData == null || groupDataBuffer == null) { return; } synchronized(groupData) { GroupItem item; Iterator<GroupItem> iterator = groupData.iterator(); while(iterator.hasNext()) { item = iterator.next(); if(item.app.packageName.equals(packageName)) { item.childrenData.clear(); item.childrenDataFiltered.clear(); iterator.remove(); } } } synchronized(groupDataBuffer) { GroupItem item; Iterator<GroupItem> iterator = groupDataBuffer.iterator(); while(iterator.hasNext()) { item = iterator.next(); if(item.app.packageName.equals(packageName)) { item.childrenData.clear(); item.childrenDataFiltered.clear(); iterator.remove(); } } } lastGetItemByAppUidIndex = -1; if(NetworkLog.filterTextInclude.length() > 0 || NetworkLog.filterTextExclude.length() > 0) { setFilter(""); } else { refreshAdapter(); } } protected void getInstalledApps(final boolean refresh) { synchronized(groupDataBuffer) { synchronized(groupData) { groupData.clear(); groupDataBuffer.clear(); synchronized(ApplicationsTracker.installedAppsLock) { for(ApplicationsTracker.AppEntry app : ApplicationsTracker.installedApps) { if(NetworkLog.state != NetworkLog.State.RUNNING && NetworkLog.initRunner.running == false) { MyLog.d("[AppFragment] Initialization aborted"); return; } GroupItem item = new GroupItem(); item.app = app; item.lastTimestamp = 0; item.childrenData = new HashMap<String, ChildItem>(); item.childrenDataFiltered = new HashMap<String, ChildItem>(); if(NetworkLogService.throughputBps) { item.throughputString = "0bps/0bps"; } else { item.throughputString = "0B/0B"; } groupData.add(item); groupDataBuffer.add(item); } } if(refresh == true) { Activity activity = getActivity(); if(activity != null) { activity.runOnUiThread(new Runnable() { public void run() { preSortData(); if(NetworkLog.filterTextInclude.length() > 0 || NetworkLog.filterTextExclude.length() > 0) { setFilter(""); } else { refreshAdapter(); } } }); } } // groupDataBuffer must always be sorted by UID for binary search Collections.sort(groupDataBuffer, new SortAppsByUid()); } } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); setUserVisibleHint(true); } public void setParent(NetworkLog parent) { this.parent = parent; } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if (this.isVisible() && !isVisibleToUser) { if(parent != null) { parent.invalidateOptionsMenu(); } } } /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MyLog.d("AppFragment onCreate"); setRetainInstance(true); preSortBy = NetworkLog.settings.getPreSortBy(); setPreSortMethod(); sortBy = NetworkLog.settings.getSortBy(); setSortMethod(); roundValues = NetworkLog.settings.getRoundValues(); groupData = new ArrayList<GroupItem>(); groupDataBuffer = new ArrayList<GroupItem>(); cachedSearchItem = new GroupItem(); cachedSearchItem.app = new ApplicationsTracker.AppEntry(); adapter = new CustomAdapter(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Context context = getActivity().getApplicationContext(); MyLog.d("[AppFragment] onCreateView"); if(NetworkLog.settings == null) { NetworkLog activity = (NetworkLog) getActivity(); if(activity != null) { activity.loadSettings(); } } LinearLayout layout = new LinearLayout(context); layout.setOrientation(LinearLayout.VERTICAL); TextView tv = new TextView(context); tv.setText(getString(R.string.app_instructions)); layout.addView(tv); listView = new ExpandableListView(context); listView.setAdapter(adapter); listView.setTextFilterEnabled(true); listView.setFastScrollEnabled(true); listView.setSmoothScrollbarEnabled(false); listView.setGroupIndicator(null); listView.setChildIndicator(null); listView.setDividerHeight(0); listView.setChildDivider(getResources().getDrawable(R.color.transparent)); layout.addView(listView); listView.setOnGroupExpandListener(new OnGroupExpandListener() { @Override public void onGroupExpand(int groupPosition) { ((GroupItem)adapter.getGroup(groupPosition)).isExpanded = true; } }); listView.setOnGroupCollapseListener(new OnGroupCollapseListener() { @Override public void onGroupCollapse(int groupPosition) { ((GroupItem)adapter.getGroup(groupPosition)).isExpanded = false; } }); listView.setOnChildClickListener(new OnChildClickListener() { @Override public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { GroupItem group = (GroupItem) adapter.getGroup(groupPosition); ChildItem child = (ChildItem) adapter.getChild(groupPosition, childPosition); getActivity().startActivity(new Intent(getActivity().getApplicationContext(), AppTimelineGraph.class) .putExtra("app_uid", group.app.uid) .putExtra("src_addr", child.receivedAddress) .putExtra("src_port", child.receivedPort) .putExtra("dst_addr", child.sentAddress) .putExtra("dst_port", child.sentPort)); return true; } }); registerForContextMenu(listView); if(gotInstalledApps == false) { getInstalledApps(true); gotInstalledApps = true; } startUpdater(); return layout; } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); ExpandableListView.ExpandableListContextMenuInfo info = (ExpandableListView.ExpandableListContextMenuInfo) menuInfo; int type = ExpandableListView.getPackedPositionType(info.packedPosition); int group = ExpandableListView.getPackedPositionGroup(info.packedPosition); int child = ExpandableListView.getPackedPositionChild(info.packedPosition); MenuInflater inflater = getActivity().getMenuInflater(); inflater.inflate(R.layout.app_context_menu, menu); if (type != ExpandableListView.PACKED_POSITION_TYPE_CHILD) { menu.findItem(R.id.app_copy_ip).setVisible(false); menu.findItem(R.id.app_whois_ip).setVisible(false); } GroupItem groupItem = (GroupItem) adapter.getGroup(group); if(NetworkLogService.toastBlockedApps.get(groupItem.app.packageName) != null) { menu.findItem(R.id.app_toggle_app_notifications).setTitle(R.string.enable_notifications); } else { menu.findItem(R.id.app_toggle_app_notifications).setTitle(R.string.disable_notifications); } if(NetworkLogService.blockedApps.get(groupItem.app.packageName) != null) { menu.findItem(R.id.app_toggle_app_logging).setTitle(R.string.unblock_app); } else { menu.findItem(R.id.app_toggle_app_logging).setTitle(R.string.block_app); } } @Override public boolean onContextItemSelected(MenuItem item) { if(!(item.getMenuInfo() instanceof ExpandableListContextMenuInfo)) return super.onContextItemSelected(item); ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) item.getMenuInfo(); int groupPos = ExpandableListView.getPackedPositionGroup(info.packedPosition); int childPos = ExpandableListView.getPackedPositionChild(info.packedPosition); ChildItem childItem; GroupItem groupItem; switch(item.getItemId()) { case R.id.app_copy_ip: childItem = (ChildItem) adapter.getChild(groupPos, childPos); copyIpAddress(childItem); return true; case R.id.app_whois_ip: childItem = (ChildItem) adapter.getChild(groupPos, childPos); whoisIpAddress(childItem); return true; case R.id.app_graph: groupItem = (GroupItem) adapter.getGroup(groupPos); showGraph(groupItem.app.uid); return true; case R.id.app_toggle_app_notifications: groupItem = (GroupItem) adapter.getGroup(groupPos); if(NetworkLogService.toastBlockedApps.remove(groupItem.app.packageName) == null) { NetworkLogService.toastBlockedApps.put(groupItem.app.packageName, groupItem.app.packageName); } new SelectToastApps().saveBlockedApps(NetworkLog.context, NetworkLogService.toastBlockedApps); return true; case R.id.app_toggle_app_logging: groupItem = (GroupItem) adapter.getGroup(groupPos); if(NetworkLogService.blockedApps.remove(groupItem.app.packageName) == null) { NetworkLogService.blockedApps.put(groupItem.app.packageName, groupItem.app.packageName); if (NetworkLogService.instance != null) { Iptables.ignoreApp(NetworkLog.context, groupItem.app.uid); } } else { if (NetworkLogService.instance != null) { Iptables.unignoreApp(NetworkLog.context, groupItem.app.uid); } } new SelectBlockedApps().saveBlockedApps(NetworkLog.context, NetworkLogService.blockedApps); return true; default: return super.onContextItemSelected(item); } } @SuppressWarnings("deprecation") void copyIpAddress(ChildItem childItem) { String hostString = ""; if(childItem.sentPackets > 0 && childItem.out != null) { String sentAddressString; String sentPortString; if(NetworkLog.resolveHosts && NetworkLog.resolveCopies) { sentAddressString = NetworkLog.resolver.resolveAddress(childItem.sentAddress); if(sentAddressString == null) { sentAddressString = childItem.sentAddress; } } else { sentAddressString = childItem.sentAddress; } if(NetworkLog.resolvePorts && NetworkLog.resolveCopies) { sentPortString = NetworkLog.resolver.resolveService(String.valueOf(childItem.sentPort)); } else { sentPortString = String.valueOf(childItem.sentPort); } hostString = sentAddressString + ":" + sentPortString; } else if(childItem.receivedPackets > 0 && childItem.in != null) { String receivedAddressString; String receivedPortString; if(NetworkLog.resolveHosts && NetworkLog.resolveCopies) { receivedAddressString = NetworkLog.resolver.resolveAddress(childItem.receivedAddress); if(receivedAddressString == null) { receivedAddressString = childItem.receivedAddress; } } else { receivedAddressString = childItem.receivedAddress; } if(NetworkLog.resolvePorts && NetworkLog.resolveCopies) { receivedPortString = NetworkLog.resolver.resolveService(String.valueOf(childItem.receivedPort)); } else { receivedPortString = String.valueOf(childItem.receivedPort); } hostString = receivedAddressString + ":" + receivedPortString; } ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE); /* newer API 11 clipboard unsupported on older devices ClipData clip = ClipData.newPlainText("NetworkLog IP Address", hostString); clipboard.setPrimaryClip(clip); */ /* use older deprecated ClipboardManager to support older devices */ clipboard.setText(hostString); } void whoisIpAddress(ChildItem childItem) { String hostString = ""; if(childItem.sentPackets > 0 && childItem.out != null) { String sentAddressString; if(NetworkLog.resolveHosts) { sentAddressString = NetworkLog.resolver.resolveAddress(childItem.sentAddress); if(sentAddressString == null) { sentAddressString = childItem.sentAddress; } } else { sentAddressString = childItem.sentAddress; } hostString = sentAddressString; } else if(childItem.receivedPackets > 0 && childItem.in != null) { String receivedAddressString; if(NetworkLog.resolveHosts) { receivedAddressString = NetworkLog.resolver.resolveAddress(childItem.receivedAddress); if(receivedAddressString == null) { receivedAddressString = childItem.receivedAddress; } } else { receivedAddressString = childItem.receivedAddress; } hostString = receivedAddressString; } startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.whois.com/whois/" + hostString))); } void showGraph(int appuid) { getActivity().startActivity(new Intent(getActivity().getApplicationContext(), AppTimelineGraph.class) .putExtra("app_uid", appuid)); } Comparator comparator = new Comparator<GroupItem>() { public int compare(GroupItem o1, GroupItem o2) { return o1.app.uid < o2.app.uid ? -1 : (o1.app.uid == o2.app.uid) ? 0 : 1; } }; public int getItemByAppUid(int uid) { if(groupDataBuffer == null) { return -1; } synchronized(groupDataBuffer) { // check to see if we need to search for index // (more often than not, the last index is still the active index being requested) if(lastGetItemByAppUidIndex < 0 || groupDataBuffer.get(lastGetItemByAppUidIndex).app.uid != uid) { cachedSearchItem.app.uid = uid; lastGetItemByAppUidIndex = Collections.binarySearch(groupDataBuffer, cachedSearchItem, comparator); } // binarySearch isn't guaranteed to return the first item of items with the same uid // so find the first item while(lastGetItemByAppUidIndex > 0) { if(groupDataBuffer.get(lastGetItemByAppUidIndex - 1).app.uid == uid) { lastGetItemByAppUidIndex--; } else { break; } } } return lastGetItemByAppUidIndex; } public void updateAppThroughputBps() { if(groupDataBuffer == null) { return; } synchronized(groupDataBuffer) { for(GroupItem item : groupDataBuffer) { if(NetworkLogService.throughputBps) { item.uploadThroughput *= Byte.SIZE; item.downloadThroughput *= Byte.SIZE; item.totalThroughput *= Byte.SIZE; } else { item.uploadThroughput /= Byte.SIZE; item.downloadThroughput /= Byte.SIZE; item.totalThroughput /= Byte.SIZE; } if(NetworkLogService.invertUploadDownload) { item.throughputString = StringUtils.formatToBytes(item.downloadThroughput) + (NetworkLogService.throughputBps ? "bps/" : "B/") + StringUtils.formatToBytes(item.uploadThroughput) + (NetworkLogService.throughputBps ? "bps" : "B"); } else { item.throughputString = StringUtils.formatToBytes(item.uploadThroughput) + (NetworkLogService.throughputBps ? "bps/" : "B/") + StringUtils.formatToBytes(item.downloadThroughput) + (NetworkLogService.throughputBps ? "bps" : "B"); } } refreshAdapter(); } } public void updateAppThroughput(int uid, long upload, long download) { if(groupDataBuffer == null) { return; } synchronized(groupDataBuffer) { int index = getItemByAppUid(uid); if(index < 0) { MyLog.d("updateAppThroughput: No app entry for " + uid); return; } GroupItem item; while(true) { item = groupDataBuffer.get(index); if(item.app.uid != uid) { break; } groupDataBufferIsDirty = true; item.uploadThroughput = upload; item.downloadThroughput = download; item.totalThroughput = upload + download; if(NetworkLogService.invertUploadDownload) { item.throughputString = StringUtils.formatToBytes(download) + (NetworkLogService.throughputBps ? "bps/" : "B/") + StringUtils.formatToBytes(upload) + (NetworkLogService.throughputBps ? "bps" : "B"); } else { item.throughputString = StringUtils.formatToBytes(upload) + (NetworkLogService.throughputBps ? "bps/" : "B/") + StringUtils.formatToBytes(download) + (NetworkLogService.throughputBps ? "bps" : "B"); } index++; if(index >= groupDataBuffer.size()) { break; } } } } public void rebuildLogEntries() { Log.d("NetworkLog", "AppFragment rebuilding entries start"); long start = System.currentTimeMillis(); stopUpdater(); synchronized(groupDataBuffer) { clear(); synchronized(NetworkLog.logFragment.listDataUnfiltered) { Iterator<LogFragment.ListItem> iterator = NetworkLog.logFragment.listDataUnfiltered.iterator(); LogEntry entry = new LogEntry(); while(iterator.hasNext()) { LogFragment.ListItem item = iterator.next(); entry.uid = item.app.uid; entry.in = item.in; entry.out = item.out; entry.proto = item.proto; entry.src = item.srcAddr; entry.dst = item.dstAddr; entry.len = item.len; entry.spt = item.srcPort; entry.dpt = item.dstPort; entry.timestamp = item.timestamp; onNewLogEntry(entry); } } groupDataBufferIsDirty = true; } startUpdater(); long elapsed = System.currentTimeMillis() - start; Log.d("NetworkLog", "AppFragment rebuilding entries end -- elapsed: " + elapsed); } // cache objects to prevent unnecessary allocations private CharArray charBuffer = new CharArray(256); private String srcKey; private String dstKey; private GroupItem newLogItem; private ChildItem newLogChild; public void onNewLogEntry(final LogEntry entry) { if(MyLog.enabled && MyLog.level >= 6) { MyLog.d(6, "AppFragment: NewLogEntry: [" + entry.uid + "] in=" + entry.in + " out=" + entry.out + " " + entry.src + ":" + entry.spt + " --> " + entry.dst + ":" + entry.dpt + " [" + entry.len + "]"); } if(groupDataBuffer == null) { return; } if(!entry.isValid()) { return; } int index = getItemByAppUid(entry.uid); if(index < 0) { MyLog.d("No app entry for uid " + entry.uid); return; } synchronized(groupDataBuffer) { try { charBuffer.reset(); charBuffer.append(entry.src).append(':').append(entry.spt).append(':').append(entry.proto).append(':'); if(entry.in != null && entry.in.length() > 0) { charBuffer.append(entry.in); } else { charBuffer.append(entry.out); } srcKey = StringPool.get(charBuffer); charBuffer.reset(); charBuffer.append(entry.dst).append(':').append(entry.dpt).append(':').append(entry.proto).append(':'); if(entry.in != null && entry.in.length() > 0) { charBuffer.append(entry.in); } else { charBuffer.append(entry.out); } dstKey = StringPool.get(charBuffer); } catch (ArrayIndexOutOfBoundsException e) { Log.e("NetworkLog", "[AppFragment.onNewEntry] charBuffer too long, skipping entry", e); return; } // generally this will iterate once, but some apps may be grouped under the same uid while(true) { newLogItem = groupDataBuffer.get(index); if(newLogItem.app.uid != entry.uid) { break; } groupDataBufferIsDirty = true; newLogItem.totalPackets++; newLogItem.totalBytes += entry.len; newLogItem.lastTimestamp = entry.timestamp; if(entry.in != null && entry.in.length() != 0) { newLogItem.receivedPackets++; newLogItem.receivedBytes += entry.len; synchronized(newLogItem.childrenData) { newLogChild = newLogItem.childrenData.get(srcKey); if(newLogChild == null) { newLogChild = new ChildItem(); newLogItem.childrenData.put(srcKey, newLogChild); } newLogChild.in = entry.in; newLogChild.out = null; newLogChild.proto = entry.proto; newLogChild.receivedPackets++; newLogChild.receivedBytes += entry.len; newLogChild.receivedTimestamp = entry.timestamp; if(MyLog.enabled && MyLog.level >= 8) { MyLog.d(8, "Added received packet index=" + index + " in=" + entry.in + " out=" + entry.out + " proto=" + entry.proto + " " + entry.src + ":" + entry.spt + " --> " + entry.dst + ":" + entry.dpt + "; total: " + newLogChild.receivedPackets + "; bytes: " + newLogChild.receivedBytes); } newLogChild.receivedPort = entry.spt; newLogChild.receivedAddress = entry.src; newLogChild.sentPort = entry.dpt; newLogChild.sentAddress = entry.dst; newLogItem.childrenNeedSort = true; } } if(entry.out != null && entry.out.length() != 0) { newLogItem.sentPackets++; newLogItem.sentBytes += entry.len; synchronized(newLogItem.childrenData) { newLogChild = newLogItem.childrenData.get(dstKey); if(newLogChild == null) { newLogChild = new ChildItem(); newLogItem.childrenData.put(dstKey, newLogChild); } newLogChild.in = null; newLogChild.out = entry.out; newLogChild.proto = entry.proto; newLogChild.sentPackets++; newLogChild.sentBytes += entry.len; newLogChild.sentTimestamp = entry.timestamp; if(MyLog.enabled && MyLog.level >= 8) { MyLog.d(8, "Added sent packet index=" + index + " in=" + entry.in + " out=" + entry.out + " " + entry.src + ":" + entry.spt + " --> " + entry.dst + ":" + entry.dpt + "; total: " + newLogChild.sentPackets + "; bytes: " + newLogChild.sentBytes); } newLogChild.receivedPort = entry.spt; newLogChild.receivedAddress = entry.src; newLogChild.sentPort = entry.dpt; newLogChild.sentAddress = entry.dst; newLogItem.childrenNeedSort = true; } } index++; if(index >= groupDataBuffer.size()) { break; } } } } public void startUpdater() { if(updater != null) { updater.stop(); } updater = new ListViewUpdater(); new Thread(updater, "AppFragmentUpdater").start(); } public void stopUpdater() { if(updater != null) { updater.stop(); } } Runnable updaterRunner = new Runnable() { public void run() { if(groupData == null) { return; } synchronized(groupData) { if(MyLog.enabled && MyLog.level >= 4) { MyLog.d(4, "AppFragmentListUpdater enter"); } if(groupDataBufferIsDirty) { preSortData(); sortData(); } if(groupDataBufferIsDirty && (NetworkLog.filterTextInclude.length() > 0 || NetworkLog.filterTextExclude.length() > 0)) { setFilter(""); } else { refreshAdapter(); } } groupDataBufferIsDirty = false; needsRefresh = false; if(MyLog.enabled && MyLog.level >= 4) { MyLog.d(4, "AppFragmentListUpdater exit"); } } }; public void updaterRunOnce() { NetworkLog.handler.post(updaterRunner); } // todo: this is largely duplicated in LogFragment -- move to its own file private class ListViewUpdater implements Runnable { boolean running = false; public void stop() { running = false; } public void run() { running = true; MyLog.d("Starting AppFragmentUpdater " + this); while(running) { if(groupDataBufferIsDirty == true || needsRefresh == true) { updaterRunOnce(); } try { Thread.sleep(1000); } catch(Exception e) { Log.d("NetworkLog", "AppFragmentListUpdater", e); } } MyLog.d("Stopped AppFragment updater " + this); } } public void setFilter(CharSequence s) { if(MyLog.enabled) { MyLog.d("[AppFragment] setFilter(" + s + ")"); } if(adapter != null) { adapter.getFilter().filter(s); } } private class CustomAdapter extends BaseExpandableListAdapter implements Filterable { LayoutInflater mInflater = (LayoutInflater) getActivity().getSystemService(Activity.LAYOUT_INFLATER_SERVICE); CustomFilter filter; private class CustomFilter extends Filter { FilterResults results = new FilterResults(); @Override protected FilterResults performFiltering(CharSequence constraint) { ArrayList<GroupItem> originalItems = new ArrayList<GroupItem>(groupDataBuffer.size()); ArrayList<GroupItem> filteredItems = new ArrayList<GroupItem>(groupDataBuffer.size()); String host; String iface; ChildItem childData; boolean matched; String sentAddressResolved; String sentPortResolved; String receivedAddressResolved; String receivedPortResolved; doNotRefresh = true; if(MyLog.enabled) { MyLog.d("[AppFragment] performFiltering"); } Log.d("NetworkLog", "[appFragment] performing filtering"); synchronized(groupDataBuffer) { originalItems.addAll(groupDataBuffer); } if(NetworkLog.filterTextInclude.length() == 0 && NetworkLog.filterTextExclude.length() == 0) { if(MyLog.enabled) { MyLog.d("[AppFragment] no constraint item count: " + originalItems.size()); } // undo uniqueHosts filtering // fixme: perhaps an array of indices into which items are filtered? for(GroupItem item : originalItems) { if(item.childrenAreFiltered) { item.childrenAreFiltered = false; item.childrenDataFiltered.clear(); item.childrenNeedSort = true; } } results.values = originalItems; results.count = originalItems.size(); } else { int count = originalItems.size(); if(MyLog.enabled) { MyLog.d("[AppFragment] item count: " + count); } if(NetworkLog.filterTextIncludeList.size() == 0) { if(MyLog.enabled) { MyLog.d("[AppFragment] no include filter, adding all items"); } for(GroupItem item : originalItems) { filteredItems.add(item); synchronized(item.childrenData) { item.childrenDataFiltered.clear(); List<String> list = new ArrayList<String>(item.childrenData.keySet()); Iterator<String> itr = list.iterator(); while(itr.hasNext()) { host = itr.next(); childData = item.childrenData.get(host); if(MyLog.enabled) { MyLog.d("[AppFragment] adding filtered host " + childData); } item.childrenDataFiltered.put(host, childData); item.childrenAreFiltered = true; } } } } else { GroupItem item; for(int i = 0; i < count; i++) { item = originalItems.get(i); // MyLog.d("[AppFragment] testing filtered item " + item + "; includes: [" + NetworkLog.filterTextInclude + "]"); boolean item_added = false; matched = false; if(NetworkLog.filterNameInclude || NetworkLog.filterUidInclude) { for(String c : NetworkLog.filterTextIncludeList) { if((NetworkLog.filterNameInclude && item.app.nameLowerCase.contains(c)) || (NetworkLog.filterUidInclude && item.app.uidString.equals(c))) { matched = true; } } } else { matched = true; } if(matched) { // test filter against address/port/iface/proto if(NetworkLog.filterAddressInclude || NetworkLog.filterPortInclude || NetworkLog.filterInterfaceInclude || NetworkLog.filterProtocolInclude) { synchronized(item.childrenData) { item.childrenDataFiltered.clear(); List<String> list = new ArrayList<String>(item.childrenData.keySet()); // todo: sort by user preference (bytes, timestamp, address, ports) Collections.sort(list); Iterator<String> itr = list.iterator(); while(itr.hasNext()) { host = itr.next(); // MyLog.d("[AppFragment] testing " + host); childData = item.childrenData.get(host); matched = false; if(NetworkLog.resolveHosts) { sentAddressResolved = NetworkLog.resolver.resolveAddress(childData.sentAddress); if(sentAddressResolved == null) { sentAddressResolved = ""; } receivedAddressResolved = NetworkLog.resolver.resolveAddress(childData.receivedAddress); if(receivedAddressResolved == null) { receivedAddressResolved = ""; } } else { sentAddressResolved = ""; receivedAddressResolved = ""; } if(NetworkLog.resolvePorts) { sentPortResolved = NetworkLog.resolver.resolveService(String.valueOf(childData.sentPort)); receivedPortResolved = NetworkLog.resolver.resolveService(String.valueOf(childData.receivedPort)); } else { sentPortResolved = ""; receivedPortResolved = ""; } if(childData.in != null && childData.in.length() > 0) { iface = childData.in; } else { iface = childData.out; } for(String c : NetworkLog.filterTextIncludeList) { if((NetworkLog.filterAddressInclude && ((childData.sentPackets > 0 && (childData.sentAddress.contains(c) || StringPool.getLowerCase(sentAddressResolved).contains(c))) || (childData.receivedPackets > 0 && (childData.receivedAddress.contains(c) || StringPool.getLowerCase(receivedAddressResolved).contains(c))))) || (NetworkLog.filterPortInclude && ((childData.sentPackets > 0 && (String.valueOf(childData.sentPort).equals(c) || StringPool.getLowerCase(sentPortResolved).equals(c))) || (childData.receivedPackets > 0 && (String.valueOf(childData.receivedPort).equals(c) || StringPool.getLowerCase(receivedPortResolved).equals(c))))) || (NetworkLog.filterInterfaceInclude && iface.contains(c)) || (NetworkLog.filterProtocolInclude && StringPool.getLowerCase(NetworkLog.resolver.resolveProtocol(childData.proto)).equals(c))) { matched = true; break; } } if(matched) { if(!item_added) { // MyLog.d("[AppFragment] adding filtered item " + item); filteredItems.add(item); item_added = true; } // MyLog.d("[AppFragment] adding filtered host " + childData); item.childrenDataFiltered.put(host, childData); item.childrenAreFiltered = true; item.childrenNeedSort = true; } } } } else { // no filtering for host/port, matches everything // MyLog.d("[AppFragment] no filter for host/port; adding filtered item " + item); filteredItems.add(item); synchronized(item.childrenData) { List<String> list = new ArrayList<String>(item.childrenData.keySet()); // todo: sort by user preference Collections.sort(list); item.childrenDataFiltered.clear(); Iterator<String> itr = list.iterator(); while(itr.hasNext()) { host = itr.next(); childData = item.childrenData.get(host); // MyLog.d("[AppFragment] adding filtered host " + childData); item.childrenDataFiltered.put(host, childData); item.childrenAreFiltered = true; item.childrenNeedSort = true; } } } } } } if(NetworkLog.filterTextExcludeList.size() > 0) { count = filteredItems.size(); GroupItem item; for(int i = count - 1; i >= 0; i--) { item = filteredItems.get(i); // MyLog.d("[AppFragment] testing filtered item: " + i + " " + item + "; excludes: [" + NetworkLog.filterTextExclude + "]"); matched = false; if(NetworkLog.filterNameExclude || NetworkLog.filterUidExclude) { for(String c : NetworkLog.filterTextExcludeList) { if((NetworkLog.filterNameExclude && item.app.nameLowerCase.contains(c)) || NetworkLog.filterUidExclude && item.app.uidString.equals(c)) { matched = true; } } } else { matched = false; } if(matched) { // MyLog.d("[AppFragment] removing filtered item: " + item); filteredItems.remove(i); continue; } if(NetworkLog.filterAddressExclude || NetworkLog.filterPortExclude || NetworkLog.filterInterfaceExclude || NetworkLog.filterProtocolExclude) { List<String> list = new ArrayList<String>(item.childrenDataFiltered.keySet()); Iterator<String> itr = list.iterator(); while(itr.hasNext()) { host = itr.next(); childData = item.childrenDataFiltered.get(host); matched = false; if(NetworkLog.resolveHosts) { sentAddressResolved = NetworkLog.resolver.resolveAddress(childData.sentAddress); if(sentAddressResolved == null) { sentAddressResolved = ""; } receivedAddressResolved = NetworkLog.resolver.resolveAddress(childData.receivedAddress); if(receivedAddressResolved == null) { receivedAddressResolved = ""; } } else { sentAddressResolved = ""; receivedAddressResolved = ""; } if(NetworkLog.resolvePorts) { sentPortResolved = NetworkLog.resolver.resolveService(String.valueOf(childData.sentPort)); receivedPortResolved = NetworkLog.resolver.resolveService(String.valueOf(childData.receivedPort)); } else { sentPortResolved = ""; receivedPortResolved = ""; } if(childData.in != null && childData.in.length() > 0) { iface = childData.in; } else { iface = childData.out; } for(String c : NetworkLog.filterTextExcludeList) { if((NetworkLog.filterAddressExclude && ((childData.sentPackets > 0 && (childData.sentAddress.contains(c) || StringPool.getLowerCase(sentAddressResolved).contains(c))) || (childData.receivedPackets > 0 && (childData.receivedAddress.contains(c) || StringPool.getLowerCase(receivedAddressResolved).contains(c))))) || (NetworkLog.filterPortExclude && ((childData.sentPackets > 0 && (String.valueOf(childData.sentPort).equals(c) || StringPool.getLowerCase(sentPortResolved).equals(c))) || (childData.receivedPackets > 0 && (String.valueOf(childData.receivedPort).equals(c) || StringPool.getLowerCase(receivedPortResolved).equals(c))))) || (NetworkLog.filterInterfaceExclude && iface.contains(c)) || (NetworkLog.filterProtocolExclude && StringPool.getLowerCase(NetworkLog.resolver.resolveProtocol(childData.proto)).equals(c))) { matched = true; break; } } if(matched) { // MyLog.d("[AppFragment] removing filtered host [" + host + "] " + childData); item.childrenDataFiltered.remove(host); } } if(item.childrenDataFiltered.size() == 0 && matched) { // MyLog.d("[AppFragment] removed all hosts, removing item from filter results"); filteredItems.remove(i); } } } } results.values = filteredItems; results.count = filteredItems.size(); } if(MyLog.enabled) { MyLog.d("[AppFragment] filter returning " + results.count + " results"); } Log.d("NetworkLog", "[AppFragment] filter returning " + results.count + " results"); return results; } @SuppressWarnings("unchecked") @Override protected void publishResults(CharSequence constraint, FilterResults results) { if(MyLog.enabled) { MyLog.d("[AppFragment] Publishing filter results"); } Log.d("NetworkLog", "[AppFragment] Publishing filter results"); synchronized(groupData) { groupData.clear(); groupData.addAll((ArrayList<GroupItem>) results.values); preSortData(); sortData(); } doNotRefresh = false; refreshAdapter(); Log.d("NetworkLog", "[AppFragment] Published"); } } @Override public CustomFilter getFilter() { if(filter == null) { filter = new CustomFilter(); } return filter; } @Override public Object getChild(int groupPosition, int childPosition) { GroupItem groupItem = groupData.get(groupPosition); HashMap<String, ChildItem> children; if(groupItem.childrenAreFiltered == false) { children = groupItem.childrenData; } else { children = groupItem.childrenDataFiltered; } if(groupItem.childrenNeedSort == true || groupItem.childrenDataSorted == null) { groupItem.childrenNeedSort = false; if(groupItem.childrenDataSorted == null || groupItem.childrenDataSorted.length < children.size()) { groupItem.childrenDataSorted = new String[children.size()]; } children.keySet().toArray(groupItem.childrenDataSorted); childrenSortMethod.setGroupItem(groupItem); Arrays.sort(groupItem.childrenDataSorted, (Comparator<String>) childrenSortMethod); } return children.get(groupItem.childrenDataSorted[childPosition]); } @Override public long getChildId(int groupPosition, int childPosition) { return childPosition; } @Override public int getChildrenCount(int groupPosition) { GroupItem groupItem = groupData.get(groupPosition); if(groupItem.childrenAreFiltered == false) { return groupItem.childrenData.size(); } else { return groupItem.childrenDataFiltered.size(); } } @Override public Object getGroup(int groupPosition) { return groupData.get(groupPosition); } @Override public int getGroupCount() { return groupData.size(); } @Override public long getGroupId(int groupPosition) { return groupPosition; } @Override public boolean hasStableIds() { return true; } @Override public boolean isChildSelectable(int arg0, int arg1) { return true; } @Override public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { GroupViewHolder holder = null; ImageView icon; TextView name; TextView throughput; TextView packets; TextView bytes; TextView timestamp; TextView hosts; GroupItem item; synchronized(groupData) { item = groupData.get(groupPosition); } if(convertView == null) { convertView = mInflater.inflate(R.layout.appitem, null); holder = new GroupViewHolder(convertView); convertView.setTag(holder); } else { holder = (GroupViewHolder) convertView.getTag(); } if(groupPosition == 0) { holder.getDivider().setVisibility(View.GONE); } else { holder.getDivider().setVisibility(View.VISIBLE); } icon = holder.getIcon(); icon.setTag(item.app.packageName); icon.setImageDrawable(ApplicationsTracker.loadIcon(getActivity().getApplicationContext(), icon, item.app.packageName)); name = holder.getName(); name.setText("(" + item.app.uid + ")" + " " + item.app.name); throughput = holder.getThroughput(); throughput.setText(getString(R.string.app_throughput) + item.throughputString); packets = holder.getPackets(); if(roundValues) { packets.setText(getString(R.string.app_packets) + StringUtils.formatToThousands(item.totalPackets) + " (" + getString(R.string.app_sent) + StringUtils.formatToThousands(item.sentPackets) + " " + getString(R.string.app_recv) + StringUtils.formatToThousands(item.receivedPackets) + ")"); } else { packets.setText(getString(R.string.app_packets) + item.totalPackets + " (" + getString(R.string.app_sent) + item.sentPackets + " " + getString(R.string.app_recv) + item.receivedPackets + ")"); } bytes = holder.getBytes(); if(roundValues) { bytes.setText(getString(R.string.app_bytes) + StringUtils.formatToBytes(item.totalBytes) + " (" + getString(R.string.app_sent) + StringUtils.formatToBytes(item.sentBytes) + " " + getString(R.string.app_recv) + StringUtils.formatToBytes(item.receivedBytes) + ")"); } else { bytes.setText(getString(R.string.app_bytes) + item.totalBytes + " (" + getString(R.string.app_sent) + item.sentBytes + " " + getString(R.string.app_recv) + item.receivedBytes + ")"); } timestamp = holder.getTimestamp(); if(item.lastTimestamp != 0) { timestamp.setText("(" + Timestamp.getTimestamp(item.lastTimestamp) + ")"); timestamp.setVisibility(View.VISIBLE); } else { timestamp.setVisibility(View.GONE); } return convertView; } @Override public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { ChildViewHolder holder = null; final TextView host; TextView sentPackets; TextView sentBytes; TextView sentTimestamp; TextView receivedPackets; TextView receivedBytes; TextView receivedTimestamp; final ChildItem item; synchronized(groupData) { item = (ChildItem) getChild(groupPosition, childPosition); } if(item == null) { if(MyLog.enabled) { MyLog.d("child (" + groupPosition + "," + childPosition + ") not found"); } return null; } if(convertView == null) { convertView = mInflater.inflate(R.layout.hostitem, null); holder = new ChildViewHolder(convertView); convertView.setTag(holder); } else { holder = (ChildViewHolder) convertView.getTag(); } host = holder.getHost(); host.setPaintFlags(host.getPaintFlags() | android.graphics.Paint.UNDERLINE_TEXT_FLAG); String hostString; final String iface; if(item.sentPackets > 0 && item.out != null && item.out.length() > 0) { host.setTag(item.sentAddress); String sentAddressString; final String sentPortString; if(NetworkLog.resolvePorts) { sentPortString = NetworkLog.resolver.resolveService(String.valueOf(item.sentPort)); } else { sentPortString = String.valueOf(item.sentPort); } if(item.proto != null && item.proto.length() > 0) { iface = NetworkLog.resolver.resolveProtocol(item.proto) + "/" + item.out; } else { iface = item.out; } if(NetworkLog.resolveHosts) { sentAddressString = NetworkLog.resolver.getResolvedAddress(item.sentAddress); if(sentAddressString == null) { NetworkResolverUpdater updater = new NetworkResolverUpdater() { public void run() { String tag = (String) host.getTag(); if(tag != null && tag.equals(item.sentAddress)) { host.setText(resolved + ":" + sentPortString + " (" + iface + ")"); } } }; sentAddressString = NetworkLog.resolver.resolveAddress(item.sentAddress, updater); if(sentAddressString == null) { sentAddressString = item.sentAddress; } } } else { sentAddressString = item.sentAddress; } hostString = sentAddressString + ":" + sentPortString + " (" + iface + ")"; } else { host.setTag(item.receivedAddress); String receivedAddressString; final String receivedPortString; if(NetworkLog.resolvePorts) { receivedPortString = NetworkLog.resolver.resolveService(String.valueOf(item.receivedPort)); } else { receivedPortString = String.valueOf(item.receivedPort); } if(item.proto != null && item.proto.length() > 0) { iface = NetworkLog.resolver.resolveProtocol(item.proto) + "/" + item.in; } else { iface = item.in; } if(NetworkLog.resolveHosts) { receivedAddressString = NetworkLog.resolver.getResolvedAddress(item.receivedAddress); if(receivedAddressString == null) { NetworkResolverUpdater updater = new NetworkResolverUpdater() { public void run() { String tag = (String) host.getTag(); if(tag != null && tag.equals(item.receivedAddress)) { host.setText(resolved + ":" + receivedPortString + " (" + iface + ")"); } } }; receivedAddressString = NetworkLog.resolver.resolveAddress(item.receivedAddress, updater); if(receivedAddressString == null) { receivedAddressString = item.receivedAddress; } } } else { receivedAddressString = item.receivedAddress; } hostString = receivedAddressString + ":" + receivedPortString + " (" + iface + ")"; } host.setText(hostString); sentPackets = holder.getSentPackets(); sentBytes = holder.getSentBytes(); sentTimestamp = holder.getSentTimestamp(); if(item.sentPackets > 0) { if(roundValues) { sentPackets.setText(StringUtils.formatToThousands(item.sentPackets)); sentBytes.setText(StringUtils.formatToBytes(item.sentBytes)); } else { sentPackets.setText(String.valueOf(item.sentPackets)); sentBytes.setText(String.valueOf(item.sentBytes)); } String timestampString = Timestamp.getTimestamp(item.sentTimestamp); sentTimestamp.setText("(" + timestampString.substring(timestampString.indexOf('-') + 1, timestampString.indexOf('.')) + ")"); sentPackets.setVisibility(View.VISIBLE); sentBytes.setVisibility(View.VISIBLE); sentTimestamp.setVisibility(View.VISIBLE); holder.getSentLabel().setVisibility(View.VISIBLE); holder.getSentPacketsLabel().setVisibility(View.VISIBLE); holder.getSentBytesLabel().setVisibility(View.VISIBLE); } else { sentPackets.setVisibility(View.GONE); sentBytes.setVisibility(View.GONE); sentTimestamp.setVisibility(View.GONE); holder.getSentLabel().setVisibility(View.GONE); holder.getSentPacketsLabel().setVisibility(View.GONE); holder.getSentBytesLabel().setVisibility(View.GONE); } receivedPackets = holder.getReceivedPackets(); receivedBytes = holder.getReceivedBytes(); receivedTimestamp = holder.getReceivedTimestamp(); if(item.receivedPackets > 0) { if(roundValues) { receivedPackets.setText(StringUtils.formatToThousands(item.receivedPackets)); receivedBytes.setText(StringUtils.formatToBytes(item.receivedBytes)); } else { receivedPackets.setText(String.valueOf(item.receivedPackets)); receivedBytes.setText(String.valueOf(item.receivedBytes)); } String timestampString = Timestamp.getTimestamp(item.receivedTimestamp); receivedTimestamp.setText("(" + timestampString.substring(timestampString.indexOf('-') + 1, timestampString.indexOf('.')) + ")"); receivedPackets.setVisibility(View.VISIBLE); receivedBytes.setVisibility(View.VISIBLE); receivedTimestamp.setVisibility(View.VISIBLE); holder.getReceivedLabel().setVisibility(View.VISIBLE); holder.getReceivedPacketsLabel().setVisibility(View.VISIBLE); holder.getReceivedBytesLabel().setVisibility(View.VISIBLE); } else { receivedPackets.setVisibility(View.GONE); receivedBytes.setVisibility(View.GONE); receivedTimestamp.setVisibility(View.GONE); holder.getReceivedLabel().setVisibility(View.GONE); holder.getReceivedPacketsLabel().setVisibility(View.GONE); holder.getReceivedBytesLabel().setVisibility(View.GONE); } return convertView; } } private class GroupViewHolder { private View mView; private ImageView mDivider = null; private ImageView mIcon = null; private TextView mName = null; private TextView mThroughput = null; private TextView mPackets = null; private TextView mBytes = null; private TextView mTimestamp = null; private TextView mUniqueHosts = null; public GroupViewHolder(View view) { mView = view; } public ImageView getDivider() { if(mDivider == null) { mDivider = (ImageView) mView.findViewById(R.id.appDivider); } return mDivider; } public ImageView getIcon() { if(mIcon == null) { mIcon = (ImageView) mView.findViewById(R.id.appIconx); } return mIcon; } public TextView getName() { if(mName == null) { mName = (TextView) mView.findViewById(R.id.appName); } return mName; } public TextView getThroughput() { if(mThroughput == null) { mThroughput = (TextView) mView.findViewById(R.id.appThroughput); } return mThroughput; } public TextView getPackets() { if(mPackets == null) { mPackets = (TextView) mView.findViewById(R.id.appPackets); } return mPackets; } public TextView getBytes() { if(mBytes == null) { mBytes = (TextView) mView.findViewById(R.id.appBytes); } return mBytes; } public TextView getTimestamp() { if(mTimestamp == null) { mTimestamp = (TextView) mView.findViewById(R.id.appLastTimestamp); } return mTimestamp; } } private class ChildViewHolder { private View mView; private TextView mHost = null; private TextView mSentLabel = null; private TextView mSentPackets = null; private TextView mSentPacketsLabel = null; private TextView mSentBytes = null; private TextView mSentBytesLabel = null; private TextView mSentTimestamp = null; private TextView mReceivedLabel = null; private TextView mReceivedPackets = null; private TextView mReceivedPacketsLabel = null; private TextView mReceivedBytes = null; private TextView mReceivedBytesLabel = null; private TextView mReceivedTimestamp = null; public ChildViewHolder(View view) { mView = view; } public TextView getHost() { if(mHost == null) { mHost = (TextView) mView.findViewById(R.id.hostName); } return mHost; } public TextView getSentLabel() { if(mSentLabel == null) { mSentLabel = (TextView) mView.findViewById(R.id.sentLabel); } return mSentLabel; } public TextView getSentPacketsLabel() { if(mSentPacketsLabel == null) { mSentPacketsLabel = (TextView) mView.findViewById(R.id.sentPacketsLabel); } return mSentPacketsLabel; } public TextView getSentBytesLabel() { if(mSentBytesLabel == null) { mSentBytesLabel = (TextView) mView.findViewById(R.id.sentBytesLabel); } return mSentBytesLabel; } public TextView getSentPackets() { if(mSentPackets == null) { mSentPackets = (TextView) mView.findViewById(R.id.sentPackets); } return mSentPackets; } public TextView getSentBytes() { if(mSentBytes == null) { mSentBytes = (TextView) mView.findViewById(R.id.sentBytes); } return mSentBytes; } public TextView getSentTimestamp() { if(mSentTimestamp == null) { mSentTimestamp = (TextView) mView.findViewById(R.id.sentTimestamp); } return mSentTimestamp; } public TextView getReceivedLabel() { if(mReceivedLabel == null) { mReceivedLabel = (TextView) mView.findViewById(R.id.receivedLabel); } return mReceivedLabel; } public TextView getReceivedPacketsLabel() { if(mReceivedPacketsLabel == null) { mReceivedPacketsLabel = (TextView) mView.findViewById(R.id.receivedPacketsLabel); } return mReceivedPacketsLabel; } public TextView getReceivedBytesLabel() { if(mReceivedBytesLabel == null) { mReceivedBytesLabel = (TextView) mView.findViewById(R.id.receivedBytesLabel); } return mReceivedBytesLabel; } public TextView getReceivedPackets() { if(mReceivedPackets == null) { mReceivedPackets = (TextView) mView.findViewById(R.id.receivedPackets); } return mReceivedPackets; } public TextView getReceivedBytes() { if(mReceivedBytes == null) { mReceivedBytes = (TextView) mView.findViewById(R.id.receivedBytes); } return mReceivedBytes; } public TextView getReceivedTimestamp() { if(mReceivedTimestamp == null) { mReceivedTimestamp = (TextView) mView.findViewById(R.id.receivedTimestamp); } return mReceivedTimestamp; } } }